#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
// hide console window on Windows in release
use std::io::Write;
use std::sync::mpsc::{self, Receiver, Sender};
use std::time::Duration;

use eframe::egui::{self, ScrollArea, Window};
use env_logger::Builder;
use tokio::runtime::Runtime;
use tokio::time::Instant;

mod gui;
mod utils;

fn main() -> Result<(), eframe::Error> {
    let mut builder = Builder::from_default_env();
    builder
        .format(|buf, record| writeln!(buf, "{} - {}", record.level(), record.args()))
        //.filter(None, LevelFilter::Info)
        .init();

    let rt = Runtime::new().expect("Unable to create Runtime");

    // Enter the runtime so that `tokio::spawn` is available immediately.
    let _enter = rt.enter();

    //// Execute the runtime in its own thread.
    //// The future doesn't have to do anything. In this example, it just sleeps forever.
    //std::thread::spawn(move || {
    //    rt.block_on(async {
    //        loop {
    //            tokio::time::sleep(Duration::from_secs(3600)).await;
    //        }
    //    })
    //});

    let options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            .with_inner_size([520.0, 356.0])
            .with_drag_and_drop(true),
        ..Default::default()
    };
    eframe::run_native(
        "AB-Path",
        options,
        Box::new(|cc| Box::new(ABPathApp::new(cc))),
    )
}

#[derive(Debug, Default)]
struct ResultInfo {
    pub ret: String,
    pub msg: String,
}

#[derive(Debug, Default)]
struct RunInfo {
    pub running: String,
}

struct ABPathApp {
    // tokio
    // Sender/Receiver for async notifications.
    tx: Sender<ResultInfo>,
    rx: Receiver<ResultInfo>,

    run_tx: Sender<RunInfo>,
    run_rx: Receiver<RunInfo>,

    pub root_path: String,
    pub text: String,
    pub ret: String,
    pub msg: String,
    pub running: u64,
    pub repaint: bool,
}

impl Default for ABPathApp {
    fn default() -> Self {
        let (tx, rx) = mpsc::channel::<ResultInfo>();
        let (run_tx, run_rx) = mpsc::channel::<RunInfo>();

        Self {
            tx,
            rx,
            run_tx,
            run_rx,
            root_path: String::default(),
            text: String::default(),
            ret: String::default(),
            msg: String::default(),
            running: 0,
            repaint: false,
        }
    }
}

impl eframe::App for ABPathApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        self.update();
        egui::CentralPanel::default().show(ctx, |ui| self.ui(ui, ctx));
    }
}

impl ABPathApp {
    pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
        // restore app state
        #[cfg(feature = "persistence")]
        if let Some(storage) = _cc.storage {
            if let Some(state) = eframe::get_value(storage, eframe::APP_KEY) {
                slf.state = state;
            }
        }

        gui::setup_custom_fonts(&cc.egui_ctx);
        gui::configure_text_styles(&cc.egui_ctx);

        let app = ABPathApp::default();
        app
    }

    fn reset_result_rs(&mut self) {
        (self.run_tx, self.run_rx) = mpsc::channel::<RunInfo>();
    }

    fn update(&mut self) {
        // Update the counter with the async response.
        if self.running > 0 {
            if let Ok(recv) = self.run_rx.try_recv() {
                self.msg = recv.running;
                self.repaint = true;
            }

            if let Ok(recv) = self.rx.try_recv() {
                self.msg = recv.msg;
                self.ret = recv.ret;
                self.running = 0;
                self.reset_result_rs();
                self.repaint = true;
            }
        }
    }

    fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
        if self.repaint {
            ctx.request_repaint();
            self.repaint = false;
        }

        let Self {
            root_path,
            text,
            ret,
            msg,
            ..
        } = self;

        ui.horizontal(|ui| {
            ui.label("工程路径 : ");
            ui.add_sized(
                [ui.available_width() - 26.0, ui.available_height()],
                egui::TextEdit::singleline(root_path).hint_text("选择或拖入工程目录"),
            );

            if ui.button("···").clicked() {
                if let Some(path) = rfd::FileDialog::new().pick_folder() {
                    *root_path = path.display().to_string();
                }
            }
        });

        ui.separator();

        ui.horizontal(|ui| {
            ui.label("输入资源名，用回车隔开.");

            ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                if self.running == 0 && ui.button("查找").clicked() {
                    *ret = String::default();
                    if root_path.is_empty() {
                        *msg = "请输入工程路径".to_string();
                    } else if text.is_empty() {
                        *msg = "请输入要查找的资源".to_string();
                    } else {
                        let vec = text
                            .split('\n')
                            .map(|val| val.to_string())
                            .collect::<Vec<String>>();
                        *msg = "查找中...".to_string();
                        self.running = 1;

                        let root_path_c = root_path.clone();
                        let tx_clone = self.tx.clone();
                        let run_tx_clone = self.run_tx.clone();
                        tokio::spawn(async move {
                            let mut send_info = ResultInfo::default();
                            let start = Instant::now();
                            let result =
                                utils::run_vec_async(&root_path_c, &vec, run_tx_clone.clone())
                                    .await;
                            if result.is_ok() {
                                send_info.ret = result.unwrap().join("\n");
                                send_info.msg = format!("查找完成，耗时 - {:?}", start.elapsed());
                            } else {
                                send_info.msg = "查找失败".to_string();
                            }

                            let _ = tx_clone.send(send_info);
                        });
                    }
                }
            });
        });

        ScrollArea::vertical()
            .id_source("text")
            .max_height(100.0)
            .show(ui, |ui| {
                ui.add_sized(
                    [ui.available_width(), ui.available_height()],
                    egui::TextEdit::multiline(text).hint_text("举例 :\nUFO_1\nUFO_2"),
                );
            });
        //egui::TextEdit::multiline(text).hint_text("举例 :\nUFO_1\nUFO_2").show(ui);

        ui.separator();

        ScrollArea::vertical()
            .id_source("ret")
            .max_height(100.0)
            .show(ui, |ui| {
                ui.add_sized(
                    [ui.available_width(), ui.available_height()],
                    egui::TextEdit::multiline(ret)
                        .desired_rows(2)
                        .hint_text(">>> 查找结果 <<<"),
                );
            });

        ui.separator();
        if self.running > 0 {
            ui.label(format!("执行 : 查找中"));

            let mut open = true;
            Window::new("查找中...")
                .open(&mut open)
                .vscroll(false)
                .title_bar(false)
                .resizable(true)
                .show(ctx, |ui| {
                    ui.label(format!("{}", msg));
                });
        } else {
            ui.label(format!("执行 : {}", msg));
        }

        preview_files_being_dropped(ctx);

        // Collect dropped files:
        ctx.input(|i| {
            if !i.raw.dropped_files.is_empty() {
                *root_path = String::from(
                    i.raw.dropped_files[0]
                        .clone()
                        .path
                        .unwrap()
                        .to_str()
                        .unwrap(),
                );
            }
        });
    }
}

/// Preview hovering files:
fn preview_files_being_dropped(ctx: &egui::Context) {
    use egui::*;
    use std::fmt::Write as _;

    if !ctx.input(|i| i.raw.hovered_files.is_empty()) {
        let text = ctx.input(|i| {
            let mut text = "Dropping files:\n".to_owned();
            for file in &i.raw.hovered_files {
                if let Some(path) = &file.path {
                    write!(text, "\n{}", path.display()).ok();
                } else if !file.mime.is_empty() {
                    write!(text, "\n{}", file.mime).ok();
                } else {
                    text += "\n???";
                }
            }
            text
        });

        let painter =
            ctx.layer_painter(LayerId::new(Order::Foreground, Id::new("file_drop_target")));

        let screen_rect = ctx.screen_rect();
        painter.rect_filled(screen_rect, 0.0, Color32::from_black_alpha(192));
        painter.text(
            screen_rect.center(),
            Align2::CENTER_CENTER,
            text,
            TextStyle::Heading.resolve(&ctx.style()),
            Color32::WHITE,
        );
    }
}
